Fix activity audit trail not recording board mutations#581
Conversation
CardService, ColumnService, BoardService, and LabelService now call IHistoryService.LogActionAsync after each successful create, update, move, delete, and archive mutation. The dependency is optional to preserve backward compatibility. Fixes #521
Adds IHistoryService registration so mutation services receive the HistoryService instance via constructor injection.
12 tests covering card CRUD+move, column CRUD, board CRUD+archive, label CRUD, and backward compatibility when IHistoryService is absent.
…board Sort board options so non-archived boards appear first, ordered by updatedAt descending, instead of alphabetical order that could default to an archived board.
Adjust existing tests for updatedAt-based ordering and add test verifying archived boards sort after non-archived boards with most-recently-active default selection.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Self-Review FindingsWhat was changed
Potential issues reviewedPerformance: The audit logging calls Error handling: If Architecture: Security: No auth bypass or data leakage issues. Audit log Test coverage: 12 backend tests cover card CRUD+move, column CRUD, board CRUD+archive, label CRUD, plus backward compatibility. Frontend has 13 tests including the new archived-board-ordering test. Full suites pass (backend + frontend). Missing coverage: No regressions found -- all existing tests continue to pass. |
There was a problem hiding this comment.
Code Review
This pull request integrates IHistoryService into several application services (BoardService, CardService, ColumnService, and LabelService) to record audit logs for entity mutations such as creation, updates, movement, and deletion. It also includes a comprehensive suite of unit tests in BoardMutationAuditTests.cs to verify these audit logs are correctly triggered. On the frontend, the useActivityQuery composable was updated to sort boards by their archived status and most recent update time. The review feedback suggests enhancing the audit logs for update operations across all services by including specific details of the changed fields (e.g., name, description, color) rather than just logging that an update occurred.
| if (_historyService != null) | ||
| await _historyService.LogActionAsync("board", board.Id, AuditAction.Updated); |
There was a problem hiding this comment.
The audit log for an update operation is more useful if it includes details about what was changed. Currently, it only logs that an update occurred without any specifics. Consider building a string of changes from the UpdateBoardDto to pass to LogActionAsync to make the activity trail more informative.
if (_historyService != null)
{
var changes = new List<string>();
if (dto.Name is not null) changes.Add($"name='{dto.Name}'");
if (dto.Description is not null) changes.Add("description updated");
if (dto.IsArchived.HasValue) changes.Add($"isArchived={dto.IsArchived.Value}");
await _historyService.LogActionAsync("board", board.Id, AuditAction.Updated, changes: string.Join("; ", changes));
}| if (_historyService != null) | ||
| await _historyService.LogActionAsync("card", card.Id, AuditAction.Updated, actorUserId); |
There was a problem hiding this comment.
The audit log for an update operation would be more useful if it included details about what was changed. Currently, it only logs that an update occurred. Consider building a string of changes from the UpdateCardDto to pass to LogActionAsync.
This would make the activity trail much more informative, especially for an entity with many updatable properties like a card.
if (_historyService != null)
{
var changes = new List<string>();
if (dto.Title is not null) changes.Add($"title='{dto.Title}'");
if (dto.Description is not null) changes.Add("description updated");
if (dto.DueDate.HasValue) changes.Add($"dueDate={dto.DueDate.Value:O}");
if (dto.IsBlocked.HasValue) changes.Add($"isBlocked={dto.IsBlocked.Value}");
if (dto.BlockReason is not null) changes.Add($"blockReason='{dto.BlockReason}'");
if (dto.LabelIds is not null) changes.Add($"labelIds=[{string.Join(",", dto.LabelIds)}]");
await _historyService.LogActionAsync("card", card.Id, AuditAction.Updated, actorUserId, string.Join("; ", changes));
}| if (_historyService != null) | ||
| await _historyService.LogActionAsync("column", column.Id, AuditAction.Updated); |
There was a problem hiding this comment.
The audit log for an update operation is more useful if it includes details about what was changed. Currently, it only logs that an update occurred. Consider building a string of changes from the UpdateColumnDto (e.g., Name, WipLimit, Position) to pass to LogActionAsync.
if (_historyService != null)
{
var changes = new List<string>();
if (dto.Name is not null) changes.Add($"name='{dto.Name}'");
if (dto.WipLimit.HasValue) changes.Add($"wipLimit={dto.WipLimit.Value}");
if (dto.Position.HasValue) changes.Add($"position={dto.Position.Value}");
await _historyService.LogActionAsync("column", column.Id, AuditAction.Updated, changes: string.Join("; ", changes));
}| if (_historyService != null) | ||
| await _historyService.LogActionAsync("label", label.Id, AuditAction.Updated); |
There was a problem hiding this comment.
The audit log for an update operation is more useful if it includes details about what was changed. Currently, it only logs that an update occurred. Consider building a string of changes from the UpdateLabelDto (Name, ColorHex) to pass to LogActionAsync.
if (_historyService != null)
{
var changes = new List<string>();
if (dto.Name is not null) changes.Add($"name='{dto.Name}'");
if (dto.ColorHex is not null) changes.Add($"colorHex='{dto.ColorHex}'");
await _historyService.LogActionAsync("label", label.Id, AuditAction.Updated, changes: string.Join("; ", changes));
}
Adversarial Review -- PR #581Critical1. Audit logging failure can crash the mutation response (all services)
Result: the entity mutation already committed to the database, but the HTTP response returns a 500 error. The client believes the operation failed and may retry, creating duplicate entities. Fix: Wrap each Example: if (_historyService != null)
{
try { await _historyService.LogActionAsync(...); }
catch (Exception) { /* log warning, don't crash */ }
}Or fix at the Major2. Double Every mutation now calls
Recommendation: Either (a) add the 3. When a board is archived or unarchived via 4. This is a board mutation that changes column positions. It has a realtime notification ( 5. Label assignment/removal on cards is not audited When Minor6.
Same issue exists for 7. The update call logs 8. No Only For example, 9. Frontend sort:
Nits10. Inconsistent Card created: 11. Interface doc comment says "SCAFFOLDING: Implementation pending"
12. Test coverage gap: no test for audit failure resilience There's a backward-compat test ( Overall AssessmentNeeds fixes before merge. The critical issue (#1) where audit failure can crash mutations must be fixed -- this is a correctness bug that could cause data duplication in production. The double- |
…vice level - Add SafeLogAsync wrapper in CardService, BoardService, ColumnService, LabelService to catch exceptions from IHistoryService — audit failure must never crash mutations - Add catch(Exception) in HistoryService.LogActionAsync for defense-in-depth - Fix UpdateBoardInternalAsync to record Archived action (not Updated) when IsArchived=true - Add audit logging to ColumnService.ReorderColumnsAsync (was missing) - Add test: CreateCard_AuditFailure_DoesNotCrashMutation - Add test: UpdateBoard_WithArchiveFlag_RecordsArchivedAction - Remove stale SCAFFOLDING comment from IHistoryService
Follow-up: Fixes pushed for Critical and Major findingsCommit Fixed
New tests added
Test resultsAll 1607 backend tests pass (0 failures). Remaining items (deferred, not blocking)
|
This reverts commit 5e064d4.
Update two analysis docs (chat-to-proposal gap and manual testing findings) to reflect recent fixes and testing status. Key changes: add Last Updated and status notes; mark Tier 1 improvements shipped (intent classifier regex/stemming/negation fixes, substring ordering bug, PR #579), UX parse hints shipped (PR #582), unit/integration tests shipped (PR #580), and note PR range #578–#582. In manual testing findings mark OBS-2/OBS-3 resolved (PR #581) and BUG-M5 resolved (PR #578), update resolutions and remove duplicate checklist items. Minor editorial clarifications and test counts added.
Summary
CardService,ColumnService,BoardService, andLabelServicenow record audit log entries viaIHistoryService.LogActionAsyncafter every successful mutation (create, update, move, delete, archive). Previously only update-conflict events were logged, causing the Activity view to show "No board activity yet" for boards with confirmed mutations.IHistoryServiceinterface so mutation services receive theHistoryServicevia constructor injection. The dependency is optional (null-safe) to preserve backward compatibility.updatedAtdescending, so the default selection is the most-recently-active non-archived board instead of the first alphabetical board (which could be archived).Test plan